package compilers.AST.statement;

import java.util.Iterator;
import java.util.LinkedList;

import compilers.AST.expression.Expression;
import compilers.AST.types.TypeSpecifier;
import compilers.compiler.PGenerator;
import compilers.exceptions.SemanticalException;
import compilers.symbolTable.SymbolTableScope;
import compilers.AST.types.MyBool;

public class ConditionStatement extends Statement {
	
	private Expression fExpression;
	private LinkedList<Statement> fIfStatements;
	private LinkedList<Statement> fElseStatements;
	boolean fHasElse = false;

	public ConditionStatement() {
		fIfStatements = new LinkedList<Statement>();
		fElseStatements = new LinkedList<Statement>();
	}
	
	public ConditionStatement(int lineNr) {
		super(lineNr);
		fIfStatements = new LinkedList<Statement>();
		fElseStatements = new LinkedList<Statement>();
	}
	
	public void setElse() {
		fHasElse = true;
	}
	
	public void print(int identLevel) {
		println(identLevel, "ConditionStatement");
		fExpression.print(identLevel+1);
		println(identLevel+1, "IfStatements");
		Iterator<Statement> it = fIfStatements.iterator();
		while (it.hasNext()) {
			it.next().print(identLevel+2);
		}
		if (!fElseStatements.isEmpty()) {
			println(identLevel+1, "ElseStatements");
			it = fElseStatements.iterator();
			while (it.hasNext()) {
				it.next().print(identLevel+2);
			}
		}
	}
	
	public void checkSemantics(SymbolTableScope currentScope) throws SemanticalException {
		fExpression.checkSemantics(currentScope);
		if (fExpression.getResultType(currentScope) != TypeSpecifier.BOOLEAN) {
			throw new SemanticalException("The result type of the expression in the condition statement should be 'boolean'.", getLine());
		}
		
		SymbolTableScope ifScope = currentScope.getChildScope(this);
		
		if (!fIfStatements.isEmpty()) {
			Iterator<Statement> it = fIfStatements.iterator();
			while (it.hasNext()) {
				it.next().checkSemantics(ifScope);
			}
		}
		if (!fElseStatements.isEmpty()) {
			SymbolTableScope elseScope = ifScope.getSiblingScope();
			Iterator<Statement> it = fElseStatements.iterator();
			while (it.hasNext()) {
				it.next().checkSemantics(elseScope);
			}
		}
	}
	
	public void setExpression(Expression expression) {
		fExpression = expression;
	}
	
	public void addIfStatement(Statement statement) {
		fIfStatements.add(statement);
	}
	
	public void addElseStatement(Statement statement) {
		fElseStatements.add(statement);
	}
	
	public void generatePcode(PGenerator pGen, SymbolTableScope currentScope) {
		SymbolTableScope ifScope = currentScope.getChildScope(this);
		SymbolTableScope elseScope = ifScope.getSiblingScope();
		
		if (fExpression.isConstant(currentScope)) {
			//Expression is constant			
			boolean exprValue = ((MyBool) fExpression.calculateValue(currentScope)).getValue();
			if (exprValue) {
				Iterator<Statement> it = fIfStatements.iterator();
				while (it.hasNext()) {
					it.next().generatePcode(pGen, ifScope);
				}
			} else {
				if (!fElseStatements.isEmpty()) {
					Iterator<Statement> it = fElseStatements.iterator();
					while (it.hasNext()) {
						it.next().generatePcode(pGen, elseScope);
					}
				}
			}
		} else {
			//Non-constant expression
			String ifLabel = "IF_" + this.fNodeId;
			String elseLabel = "ELSE_" + this.fNodeId;
			fExpression.generatePcode(pGen, currentScope);
			
			if(elseScope != null) {
				pGen.conditionalJump("begin_"+elseLabel);
			} else {
				pGen.conditionalJump("end_"+ifLabel);
			}
			
			pGen.beginLabel(ifLabel);

			//Generate instructions within if-scope
			Iterator<Statement> it = fIfStatements.iterator();
			while (it.hasNext()) {
				it.next().generatePcode(pGen, ifScope);
			}
			
			if(elseScope != null) {
				pGen.unconditionalJump("end_"+elseLabel);
			}
			pGen.endLabel(ifLabel);
			
			//Generate instructions for else-scope if it exists
			if(elseScope != null) {
				pGen.beginLabel(elseLabel);
				
				it = fElseStatements.iterator();
				while (it.hasNext()) {
					it.next().generatePcode(pGen, elseScope);
				}
				
				pGen.endLabel(elseLabel);
			}
		}
	}
	
	public boolean checkReturn(SymbolTableScope currentScope, int typeExpected, String functionName) throws SemanticalException {
		boolean ifFound = false;
		boolean elseFound = false;
		
		//First iterate statements to check if there is a return.
		SymbolTableScope ifScope = currentScope.getChildScope(this);
		Iterator<Statement> it = fIfStatements.iterator();
		while(it.hasNext()) {
			Statement curStatement = it.next();
			if(curStatement instanceof ReturnStatement) {
				ReturnStatement returnStat = (ReturnStatement) curStatement;
				if(returnStat.getResultType(ifScope) != typeExpected) {
					throw new SemanticalException("Return statement of type '" + TypeSpecifier.getType(returnStat.getResultType(ifScope)) + "' doesn't match with the required return type '" + TypeSpecifier.getType(typeExpected) + "' of function '" + functionName + "'", returnStat.getLine());
				} else {
					ifFound = true;
				}
			}
		}
		if(fHasElse) {
			SymbolTableScope elseScope = ifScope.getSiblingScope();
			it = fElseStatements.iterator();
			while(it.hasNext()) {
				Statement curStatement = it.next();
				if(curStatement instanceof ReturnStatement) {
					ReturnStatement returnStat = (ReturnStatement) curStatement;
					if(returnStat.getResultType(elseScope) != typeExpected) {
						throw new SemanticalException("Return statement of type '" + TypeSpecifier.getType(returnStat.getResultType(elseScope)) + "' doesn't match with the required return type '" + TypeSpecifier.getType(typeExpected) + "' of function '" + functionName + "'", returnStat.getLine());
					} else {
						elseFound = true;
					}
				}
			}
		}
		
		//Return true if return statements found, otherwise recursively check the nested condition scopes.
		if(fHasElse && ifFound && elseFound) {
			return true;
		} else if(!fHasElse && ifFound) {
			return true;
		} else {
			boolean newScopeFound = false;
			if(fIfStatements.size() == 0) {
				return false;
			}
			it = fIfStatements.iterator();
			while(it.hasNext()) {
				Statement curStatement = it.next();
				if(curStatement instanceof ConditionStatement) {
					ConditionStatement newCond = (ConditionStatement) curStatement;
					if(!newCond.checkReturn(ifScope, typeExpected, functionName)) {
						return false;
					}
					if(!newCond.fHasElse && !ifFound) {
						return false;
					}
					newScopeFound = true;
				}
			}
			if(fHasElse) {
				if(fElseStatements.size() == 0) {
					return false;
				}
				it = fElseStatements.iterator();
				SymbolTableScope elseScope = ifScope.getSiblingScope();
				while(it.hasNext()) {
					Statement curStatement = it.next();
					if(curStatement instanceof ConditionStatement) {
						if(!((ConditionStatement) curStatement).checkReturn(elseScope, typeExpected, functionName)) {
							return false;
						}
					}
				}
			}
			if(!ifFound && !newScopeFound) {
				return false;
			}
		}
		
		return true;
	}
	
}
